// --- Copyright (c) 2010 Steven Hartgers ---
// Copyright (C) 2010 Steven Hartgers All rights are reserved.
// Author contact: fiinix00/at\gmail.com
// ------------------------------------------------------------------------
// This program is free software: you can redistribute it and/or modify 
// it under the terms of the GNU General Public License as published by 
// the Free Software Foundation, either version 3 of the License, or 
// (at your option) any later version. 
//  
// This program is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied warranty of 
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
// GNU General Public License for more detail
//  
// You should have received a copy of the GNU General Public License 
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
// ------------------------------------------------------------------------
// COPYRIGHT NOTICE: This copyright notice may NOT be removed, obscured or modified 
// without written consent from the author.
// --- End copyright notice --- 


using System;
using System.IO;
using System.Net.Sockets;

namespace Hack.Net.Smtp
{
    /** This thread does the real work.*/
    public class SmtpReceiving
    {
        /** Properties of SMTP Server */
        readonly SmtpProperties prop;

        /** Communication writer */
        StreamWriter writer;
        /** Communication reader */
        StreamReader reader;

        const string badsequence = "503 bad sequence of commands";
        const string cmdnotrecognized = "500 command not recognized";

        /** Received ehlo string */
        string ehlo;
        /** Received mail-from */
        string mailfrom;
        /** Received rcpto */
        string rcpto;
        /** Received data message*/
        string data;

        /** Is this request a valid request? */
        bool isValid = true;


        /** Is this request a valid request? */
        public bool IsValid
        {
            get
            {
                return isValid;
            }
        }


        /** The host given in Ehlo message */
        public string EhloHost
        {
            get
            {
                try
                {
                    return ehlo.Split(' ')[1];
                }
                catch (Exception)
                {
                    return null;
                }
            }
        }

        /** The sender given in MAIL FROM: part */
        public string MailFromReversePath
        {
            get
            {
                try
                {
                    return mailfrom.Split('<')[1].Split('>')[0];
                }
                catch (Exception)
                {
                    return null;
                }
            }
        }

        /** Parameters given in MAIL FROM: part */
        public string MailFromParameters
        {
            get
            {
                try
                {
                    return mailfrom.Split('<')[2].Split('>')[0];
                }
                catch (Exception)
                {
                    return null;
                }
            }
        }

        /** The receiver given in RECP TO: part */
        public string RcpToForwardPath
        {
            get
            {
                try
                {
                    return rcpto.Split('<')[1].Split('>')[0];
                }
                catch (Exception)
                {
                    return null;
                }
            }
        }

        /** The host given in RECP TO: part */
        public string RcpToForwardHost
        {
            get
            {
                try
                {
                    return RcpToForwardPath.Split('@')[1];
                }
                catch (Exception)
                {
                    return null;
                }

            }
        }


        /** Parameters in RCP TO: part */
        public string RcpToParameters
        {
            get
            {
                try
                {
                    return rcpto.Split('<')[2].Split('>')[0];
                }
                catch (Exception)
                {
                    return null;
                }
            }
        }

        /** Data received */
        public string Data
        {
            get
            {
                try
                {
                    string back = data.Substring(4).Trim();
                    return back.Substring(0, back.Length - 3);
                }
                catch (Exception)
                {
                    return null;
                }
            }
        }



        /** Create new smtp request and get data from connected client. */
        public SmtpReceiving(TcpClient client, SmtpProperties prop)
        {
            this.prop = prop;
            this.isValid = false;

            //Set up readers and writers to talk with client
            NetworkStream stream = client.GetStream();
            this.writer = new StreamWriter(stream);
            this.reader = new StreamReader(stream);


            //Talk with client about accepting the mail...
            HandleProtocol();
        }


        /** Handle the protocol: Small talk with client...
         * The grounding document is: RFC 2821*/
        private void HandleProtocol()
        {
            //Reject every incoming mail
            if (prop.autoreject)
            {
                //Reject the mail
                sendMessage("554", prop.greetings);
                processQuit();
                return;
            }


            //Send greetings message
            sendMessage("220", prop.greetings);

            if (!processEhlo())
                return;

            if (!processMailFrom())
                return;

            if (!processRcpto())
                return;

            if (!processData())
                return;

            //Wait for quit
            if (!processQuit())
                return;

            //Now everything was valid, for now.
            isValid = true;
        }


        /** Send message or command to client */
        private void sendMessage(string cmd)
        {
            sendMessage(cmd, "");
        }

        /** Send message or command to client */
        private void sendMessage(string cmd, string msg)
        {
            writer.WriteLine("{0} {1}", cmd, msg);
            writer.Flush();
        }


        /** Reject the mail. RFC 2821 says we have to wait for QUIT. */
        private bool processQuit()
        {
            //Everything ok?
            bool ret = true;

            //Wait for quit from client. Every command which is not QUIT
            //should be answerd with error messge according to RFC 2821.
            while (!reader.ReadLine().ToUpper().Equals("QUIT"))
            {
                ret = false;
                sendMessage(badsequence);
            }

            sendMessage("221", "see you.");
            return ret;
        }


        /** Return true when everything ok. False if error occured */
        private bool processEhlo()
        {
            //Get ehlo message
            ehlo = reader.ReadLine();
            //If ehlo message does not start with EHLO...
            if (!ehlo.ToUpper().StartsWith("EHLO "))
            {
                //notifiy client
                sendMessage(cmdnotrecognized);
                //And try it once more
                ehlo = reader.ReadLine();

                //Once again false --> quit
                if (!ehlo.ToUpper().StartsWith("EHLO "))
                {
                    //Send greetings message
                    sendMessage("554", "wrong EHLO message");
                    processQuit();
                    return false;
                }
            }

            //Send response
            sendMessage("250", "Hello " + EhloHost + ", give it to me baby!");

            return true;
        }


        /** Return true when everything ok. False if error occured */
        private bool processMailFrom()
        {
            //Wrong line here!
            if (!reader.ReadLine().ToUpper().StartsWith("MAIL FROM:"))
            {
                //Notify client
                sendMessage(badsequence);
                processQuit();
                return false;
            }

            //Otherwise notify the sender in good manner
            sendMessage("250", "OK");

            return true;
        }


        /** Return true when everything ok. False if error occured */
        private bool processRcpto()
        {
            //Get rcpto message
            rcpto = reader.ReadLine();

            //Wrong line here!
            if (!rcpto.ToUpper().StartsWith("RCPT TO:"))
            {
                //Notify client
                sendMessage(badsequence);
                processQuit();
                return false;
            }

            //Otherwise notify the sender in good manner
            sendMessage("250", "OK");

            return true;
        }


        /** Return true when everything ok. False if error occured */
        private bool processData()
        {
            //Get rcpto message
            data = reader.ReadLine() + "\r\n";

            //Wrong line here!
            if (!data.ToUpper().StartsWith("DATA"))
            {
                //Notify client
                sendMessage("missing start sequence 'DATA <CR><LF>'");
                processQuit();
                return false;
            }

            //Otherwise notify the sender immediately
            sendMessage("350");

            //As long as the end of data has not been detected, read new lines...
            while (!data.EndsWith("\r\n.\r\n"))
            {
                data += reader.ReadLine() + "\r\n";
            }

            //Data received, notify client
            sendMessage("250", "OK");

            return true;
        }
    }



    /** This struct contains properties of the SMTP Server */
    public class SmtpProperties
    {
        /** The listening port */
        public int port = 25;

        /** Auto reject every incoming mail */
        public bool autoreject = false;

        /** The greetings message which is send at the beginning.
         * If autoreject is on, this string should also contain
         * some info on it, according to RFC 2821.*/
        public string greetings = "";
    }

}